/*
 * Class contains main methods for creating and basic
 * mathematical manipulation with intevals from interval
 * arithmetic of Kaucher.
 * 
 */

package intervalMath;
import java.math.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author
 */
public class KaucherIntervalArithmetic implements IntervalArithmetic {

    private BigDecimal leftLimit;
    private BigDecimal rightLimit;
    
    private RoundingMode properLeftRoundingMode;
    private RoundingMode properRightRoundingMode;
    private RoundingMode improperLeftRoundingMode;
    private RoundingMode improperRightRoundingMode;
    private int scale = 5;
    private IntervalRoundingMode intervalRoundingMode = IntervalRoundingMode.OUTWARD;
    

    public KaucherIntervalArithmetic() {
        this.leftLimit = BigDecimal.ZERO;
        this.rightLimit = BigDecimal.ZERO;
        this.roundingConfiguration(this.intervalRoundingMode);
    }

    public KaucherIntervalArithmetic(double left, double right) {
        this.leftLimit = new BigDecimal(Double.toString(left));
        this.rightLimit = new BigDecimal(Double.toString(right));
        this.scale = Math.max(this.leftLimit.scale(), this.rightLimit.scale());
        this.roundingConfiguration(this.intervalRoundingMode);
        this.roundInterval();
    }
    
    public KaucherIntervalArithmetic(
        double left,
        double right,
        IntervalRoundingMode intervalRoundingMode
    ) {
        this.leftLimit = new BigDecimal(Double.toString(left));
        this.rightLimit = new BigDecimal(Double.toString(right));
        this.scale = Math.max(this.leftLimit.scale(), this.rightLimit.scale());
        this.intervalRoundingMode = intervalRoundingMode;
        this.roundingConfiguration(this.intervalRoundingMode);
        this.roundInterval();
    }
    
    public KaucherIntervalArithmetic(
        double left,
        double right,
        IntervalRoundingMode intervalRoundingMode,
        int scale
    ) {
        this.leftLimit = new BigDecimal(Double.toString(left));
        this.rightLimit = new BigDecimal(Double.toString(right));
        this.intervalRoundingMode = intervalRoundingMode;
        this.scale = scale;
        this.roundingConfiguration(intervalRoundingMode);
        this.roundInterval();
    }
    
    public KaucherIntervalArithmetic(String left, String right) {
        this.leftLimit = new BigDecimal(left);
        this.rightLimit = new BigDecimal(right);
        this.scale = Math.max(this.leftLimit.scale(), this.rightLimit.scale());
        this.roundingConfiguration(this.intervalRoundingMode);
        this.roundInterval();
    }
    
    public KaucherIntervalArithmetic(
        String left,
        String right,
        IntervalRoundingMode intervalRoundingMode
    ) {
        this.leftLimit = new BigDecimal(left);
        this.rightLimit = new BigDecimal(right);
        this.intervalRoundingMode = intervalRoundingMode;
        this.scale = Math.max(this.leftLimit.scale(), this.rightLimit.scale());
        this.roundingConfiguration(intervalRoundingMode);
        this.roundInterval();
    }
    
    public KaucherIntervalArithmetic(
        String left,
        String right,
        IntervalRoundingMode intervalRoundingMode,
        int scale
    ) {
        this.leftLimit = new BigDecimal(left);
        this.rightLimit = new BigDecimal(right);
        this.intervalRoundingMode = intervalRoundingMode;
        this.scale = scale;
        this.roundingConfiguration(intervalRoundingMode);
        this.roundInterval();
    }


    public KaucherIntervalArithmetic(BigDecimal left, BigDecimal right) {
        this.leftLimit = left;
        this.rightLimit = right;
        this.scale = Math.max(this.leftLimit.scale(), this.rightLimit.scale());
        this.roundingConfiguration(this.intervalRoundingMode);
        this.roundInterval();
    }

    public KaucherIntervalArithmetic(
        BigDecimal left,
        BigDecimal right,
        IntervalRoundingMode intervalRoundingMode
    ) {
        this.leftLimit = left;
        this.rightLimit = right;
        this.scale = Math.max(leftLimit.scale(), rightLimit.scale());
        this.intervalRoundingMode = intervalRoundingMode;
        this.roundingConfiguration(intervalRoundingMode);
        this.roundInterval();
    }
    
    public KaucherIntervalArithmetic(
        BigDecimal left,
        BigDecimal right,
        IntervalRoundingMode intervalRoundingMode,
        int scale
    ) {
        this.leftLimit = left;
        this.rightLimit = right;
        this.scale = scale;
        this.intervalRoundingMode = intervalRoundingMode;
        this.roundingConfiguration(intervalRoundingMode);
        this.roundInterval();
    }

    public KaucherIntervalArithmetic(String interaval) throws IntervalException {
        Pattern valid = Pattern.compile("\\[.+;.+\\]");
        Matcher m = valid.matcher(interaval);
        boolean isValid = m.matches();
        if (!isValid){
            throw new IntervalException("Неправильный формат строки!");
        }
        interaval = interaval.substring(1, interaval.length()-1);
        Pattern p = Pattern.compile(";");
        String[] limits = p.split(interaval);
        try {
            this.leftLimit = new BigDecimal(limits[0]);
            this.rightLimit = new BigDecimal(limits[1]);
        } catch (NumberFormatException e) {
            throw e;
        }
        this.scale = Math.max(this.leftLimit.scale(), this.rightLimit.scale());
        this.roundingConfiguration(this.intervalRoundingMode);
        this.roundInterval();
    }

    /**
     * Defines whether left limit of the interval is less than or equal right limit 
     * @return boolean
     */
    @Override
    public boolean isProper() {
        return (this.leftLimit.compareTo(this.rightLimit) <= 0);
    }
    
    /**
     * Defines whether the interval is degenerate 
     * @return boolean
     */
    public boolean isNumber() {
        return (this.leftLimit.compareTo(this.rightLimit) == 0);
    }

    /**
     * Returns projection of the interval
     * @return KaucherIntervalArithmetic
     */
    public KaucherIntervalArithmetic pro() {
        if (this.isProper()) {
            return (KaucherIntervalArithmetic)this.clone();
        } else {
            return this.dual();
        }
    }

    /**
     * Returns inverse interval
     * @return KaucherIntervalArithmetic
     * @throws IntervalException 
     */
    public KaucherIntervalArithmetic inv(int scale) throws IntervalException {        
        if (this.isContainsInZero() || this.isZeroContaining()) {
            throw new IntervalException("Interval doesn't have inverse");
        }        
        RoundingMode forLeft = this.improperLeftRoundingMode;
        RoundingMode forRight = this.improperRightRoundingMode;
        if (this.isNumber() || !this.isProper()) {
            forLeft = this.properLeftRoundingMode;
            forRight = this.properRightRoundingMode;
        } 
        BigDecimal left = BigDecimal.ONE.divide(this.leftLimit, scale, forLeft);
        BigDecimal right = BigDecimal.ONE.divide(this.rightLimit, scale, forRight);
        return new KaucherIntervalArithmetic(
            left,
            right,
            this.intervalRoundingMode,
            scale
        );       
    }

    /**
     * Dualization of interval
     * @return KaucherIntervalArithmetic
     */
    public KaucherIntervalArithmetic dual() {
        return new KaucherIntervalArithmetic(
            this.rightLimit,
            this.leftLimit,
            this.intervalRoundingMode,
            this.scale
        );
    }

    /**
     * Calculate the opposite by addition interval
     * @return KaucherIntervalArithmetic
     */
    public KaucherIntervalArithmetic opp() {        
        return new KaucherIntervalArithmetic(
            this.leftLimit.negate(),
            this.rightLimit.negate(),
            this.intervalRoundingMode,
            this.scale
        );
    }
    
     /**
     * Calculate the sum of this and b
     * @param b IntervalArithmetic
     * @return KaucherIntervalArithmetic
     */
    @Override
    public IntervalArithmetic add(IntervalArithmetic b) {
        return this.add(
            (KaucherIntervalArithmetic)b,            
            Math.max(this.getScale(), b.getScale())
        );        
    }

    /**
     * Calculate the sum of this and b
     * @param b IntervalArithmetic
     * @param scale int
     * @return KaucherIntervalArithmetic
     */
    @Override
    public IntervalArithmetic add(IntervalArithmetic b, int scale) {
        return new KaucherIntervalArithmetic(
            this.leftLimit.add(b.getLeftLimit()),
            this.rightLimit.add(b.getRightLimit()),
            this.intervalRoundingMode,
            scale
        );
    }
    
    /**
     * Calculate multiply interval on given double number
     * @param m double
     * @param scale int
     * @return KaucherIntervalArithmetic the result interval with scale = scale
     */
    @Override
    public KaucherIntervalArithmetic multDouble(double m, int scale) {
        BigDecimal mult = new BigDecimal(Double.toString(m));
        if (m >= 0) {
            return new KaucherIntervalArithmetic(
                this.leftLimit.multiply(mult),
                this.rightLimit.multiply(mult),
                this.intervalRoundingMode,
                scale
            );
        } else {
            return new KaucherIntervalArithmetic(
                this.rightLimit.multiply(mult),
                this.leftLimit.multiply(mult),
                this.intervalRoundingMode,
                scale
            );
        }
    }
    
     /**
     * Calculate multiply interval on given double number
     * @param m double
     * @return KaucherIntervalArithmetic the result interval
     */
    @Override
    public KaucherIntervalArithmetic multDouble(double m) {
        BigDecimal mult = new BigDecimal(Double.toString(m));
        return this.multDouble(m, Math.max(this.scale, mult.scale()));
    }
    
     /**
     * Subtract from interval another given interval
     * @param b IntervalArithmetic
     * @return KaucherIntervalArithmetic
     */
    @Override
    public IntervalArithmetic sub(IntervalArithmetic b) {
        return this.sub(b, Math.max(this.getScale(), b.getScale()));
    }
    
    /**
     * Subtract from interval another given interval
     * @param b IntervalArithmetic
     * @param scale int
     * @return KaucherIntervalArithmetic
     */
    @Override
    public IntervalArithmetic sub(IntervalArithmetic b, int scale) {
        return new KaucherIntervalArithmetic(
            this.leftLimit.subtract(b.getRightLimit()),
            this.rightLimit.subtract(b.getLeftLimit()),
            this.intervalRoundingMode,
            scale
        );
    }
    
    /**
     * Implements inner subtract
     * @param b
     * @return KaucherIntervalArithmetic
     */
    public KaucherIntervalArithmetic innerSub(KaucherIntervalArithmetic b) {
        return (KaucherIntervalArithmetic)this.add(b.opp());
    }

    /**
     * Implements inner subtract
     * @param b
     * @return KaucherIntervalArithmetic
     */
    public KaucherIntervalArithmetic innerSub(KaucherIntervalArithmetic b, int scale) {
        return (KaucherIntervalArithmetic)this.add(b.opp(), scale);
    }
    
     /**
     * Define whether left and right limits of the interval greater than zero.
     * Interval is belong to class P.
     * @return boolean
     */
    public boolean isNonnegative() {        
        int isLeft = this.leftLimit.compareTo(BigDecimal.ZERO);
        int isRight = this.rightLimit.compareTo(BigDecimal.ZERO);
        return (isLeft >= 0 && isRight >= 0);
    }

    /**
     * Define whether after multiplying on -1 interval is belong to class P.
     * Interval is belong to class -P.
     * @return boolean
     */
    public boolean isNonpositive() {
        int isLeft = this.leftLimit.negate().compareTo(BigDecimal.ZERO);
        int isRight = this.rightLimit.negate().compareTo(BigDecimal.ZERO);
        return (isLeft >= 0 && isRight >= 0);
    }

     /**
     * Define whether right limit of interval greater than zero and left limit less than zero.
     * Interval is belong to class Z.
     * @return boolean
     */
    public boolean isZeroContaining() {
        int isLeft = this.leftLimit.compareTo(BigDecimal.ZERO);
        int isRight = this.rightLimit.compareTo(BigDecimal.ZERO);
        return (isLeft <= 0 && isRight >= 0);
    }

    /**
     * Define whether after dualization the interval is belong to class Z.
     * Interval is belong to class dualZ.
     * @return boolean
     */
    public boolean isContainsInZero() {
        int isLeft = this.leftLimit.compareTo(BigDecimal.ZERO);
        int isRight = this.rightLimit.compareTo(BigDecimal.ZERO);
        return (isLeft >= 0 && isRight <= 0);        
    }

    @Override
    public Object clone() {
        return new KaucherIntervalArithmetic(
            this.leftLimit,
            this.rightLimit,
            this.intervalRoundingMode,
            this.scale
        );
    }
    
    @Override
    public boolean equals(Object o) {
        KaucherIntervalArithmetic interval = (KaucherIntervalArithmetic)o;
        return (
           this.leftLimit.compareTo(interval.getLeftLimit()) == 0
           && this.rightLimit.compareTo(interval.getRightLimit()) == 0
        );
        
    }
    
    /**
     * Lakeev multiplying
     * @param b KaucherIntervalArithmetic
     * @param scale int
     * @return KaucherIntervalArithmetic
     */
    public KaucherIntervalArithmetic multLakeev(KaucherIntervalArithmetic b, int scale) {
        BigDecimal aLP = positivePart(this.leftLimit);
        BigDecimal aLN = negativePart(this.leftLimit);
        BigDecimal aRP = positivePart(this.rightLimit);
        BigDecimal aRN = negativePart(this.rightLimit);
        BigDecimal bLP = positivePart(b.getLeftLimit());
        BigDecimal bLN = negativePart(b.getLeftLimit());
        BigDecimal bRP = positivePart(b.getRightLimit());
        BigDecimal bRN = negativePart(b.getRightLimit());
        if ((this.isProper() && !b.isProper()) || (!this.isProper() && b.isProper())) {
            return new KaucherIntervalArithmetic(
                aLP.multiply(bLP).add(aRN.multiply(bRN)).subtract(aRP.multiply(bLN)).subtract(aLN.multiply(bRP)),
                aRP.multiply(bRP).add(aLN.multiply(bLN)).subtract(aLP.multiply(bRN)).subtract(aRN.multiply(bLP)),
                this.intervalRoundingMode,
                scale
            );
        }
        if (this.isProper() || b.isProper()) {
            return new KaucherIntervalArithmetic(
                aLP.multiply(bLP).add(aRN.multiply(bRN)).subtract(aRP.multiply(bLN).max(aLN.multiply(bRP))),
                aRP.multiply(bRP).max(aLN.multiply(bLN)).subtract(aLP.multiply(bRN)).subtract(aRN.multiply(bLP)),
                this.intervalRoundingMode,
                scale
            );
        }
        return new KaucherIntervalArithmetic(
            aLP.multiply(bLP).max(aRN.multiply(bRN)).subtract(aRP.multiply(bLN).max(aLN.multiply(bRP))),
            aRP.multiply(bRP).max(aLN.multiply(bLN)).subtract(aLP.multiply(bRN).max(aRN.multiply(bLP))),
            this.intervalRoundingMode,
            scale
        );
    }
    
    /**
     * Lakeev multiplying
     * @param b KaucherIntervalArithmetic     
     * @return KaucherIntervalArithmetic
     */
    public KaucherIntervalArithmetic multLakeev(KaucherIntervalArithmetic b) {
        return this.multLakeev(b, this.getScale() + b.getScale());
    }
    
    /**
     * Multiplying
     * @param arg IntervalArithmetic
     * @param scale int
     * @return KaucherIntervalArithmetic      
     */
    @Override
    public KaucherIntervalArithmetic mult(IntervalArithmetic arg, int scale) {
        KaucherIntervalArithmetic b = (KaucherIntervalArithmetic)arg;        
        if (this.isNonnegative()) {
            if (b.isNonnegative()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getLeftLimit()),
                    this.rightLimit.multiply(b.getRightLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isZeroContaining()) {
                return new KaucherIntervalArithmetic(
                    this.rightLimit.multiply(b.getLeftLimit()),
                    this.rightLimit.multiply(b.getRightLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isNonpositive()) {
                return new KaucherIntervalArithmetic(
                    this.rightLimit.multiply(b.getLeftLimit()),
                    this.leftLimit.multiply(b.getRightLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isContainsInZero()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getLeftLimit()),
                    this.leftLimit.multiply(b.getRightLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
        }
        if (this.isZeroContaining()) {
            if (b.isNonnegative()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getRightLimit()),
                    this.rightLimit.multiply(b.getRightLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isZeroContaining()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getRightLimit()).min(this.rightLimit.multiply(b.getLeftLimit())),
                    this.leftLimit.multiply(b.getLeftLimit()).max(this.rightLimit.multiply(b.getRightLimit())),
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isNonpositive()) {
                return new KaucherIntervalArithmetic(
                    this.rightLimit.multiply(b.getLeftLimit()),
                    this.leftLimit.multiply(b.getLeftLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }           
        }
        if (this.isNonpositive()) {
            if (b.isNonnegative()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getRightLimit()),
                    this.rightLimit.multiply(b.getLeftLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isZeroContaining()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getRightLimit()),
                    this.leftLimit.multiply(b.getLeftLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isNonpositive()) {
                return new KaucherIntervalArithmetic(
                    this.rightLimit.multiply(b.getRightLimit()),
                    this.leftLimit.multiply(b.getLeftLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isContainsInZero()) {
                return new KaucherIntervalArithmetic(
                    this.rightLimit.multiply(b.getRightLimit()),
                    this.rightLimit.multiply(b.getLeftLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
        }
        if (this.isContainsInZero()) {
            if (b.isNonnegative()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getLeftLimit()),
                    this.rightLimit.multiply(b.getLeftLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isNonpositive()) {
                return new KaucherIntervalArithmetic(
                    this.rightLimit.multiply(b.getRightLimit()),
                    this.leftLimit.multiply(b.getRightLimit()), 
                    this.intervalRoundingMode,
                    scale
                );
            }
            if (b.isContainsInZero()) {
                return new KaucherIntervalArithmetic(
                    this.leftLimit.multiply(b.getLeftLimit()).max(this.rightLimit.multiply(b.getRightLimit())),
                    this.leftLimit.multiply(b.getRightLimit()).min(this.rightLimit.multiply(b.getLeftLimit())),
                    this.intervalRoundingMode,
                    scale
                );
            }
        }
        return new KaucherIntervalArithmetic(0, 0, this.intervalRoundingMode, scale);        
    }
    
    /**
     * Multiplying
     * @param arg IntervalArithmetic     
     * @return KaucherIntervalArithmetic     
     */
    @Override
    public KaucherIntervalArithmetic mult(IntervalArithmetic arg) {
        return this.mult(arg, this.getScale() + arg.getScale());
    }
    
    /**
     * Divides on given interval
     * @param b IntervalArithmetic
     * @return KaucherIntervalArithmetic
     * @throws IntervalException 
     */
    @Override
    public KaucherIntervalArithmetic div(
        IntervalArithmetic b,
        int scale
    ) throws IntervalException {
        KaucherIntervalArithmetic arg = (KaucherIntervalArithmetic)b;
        if (arg.isContainsInZero() || arg.isZeroContaining()) {
            throw new IntervalException("pro(divider) contains zero");
        }
        RoundingMode forLeft = this.properLeftRoundingMode;
        RoundingMode forRight = this.properRightRoundingMode;
        if (!arg.isProper()) {
            forLeft = this.improperLeftRoundingMode;
            forRight = this.improperRightRoundingMode;
        } 
        BigDecimal left = BigDecimal.ONE.divide(arg.getRightLimit(), scale, forLeft);
        BigDecimal right = BigDecimal.ONE.divide(arg.getLeftLimit(), scale, forRight);
        KaucherIntervalArithmetic i =  new KaucherIntervalArithmetic(
            left,
            right,
            this.intervalRoundingMode            
        );
        return this.mult(i);
    }   

    /**
     * Implements inner division of Kaucher Interval Arithmetic
     * @param b
     * @return KaucherIntervalArithmetic
     * @throws IntervalException 
     */
    public KaucherIntervalArithmetic innerDiv(
        KaucherIntervalArithmetic b,
        int scale
    ) throws IntervalException {
        try {
            return this.mult(b.inv(scale));
        } catch (IntervalException e) {
            throw new IntervalException("Inner division on zero-containing interval");
        }
    }
    
     /**
     * Calculate whether interval is contained in given interval
     * @param b KaucherIntervalArithmetic
     * @return boolean
     */
    @Override
    public boolean isContainedIn(IntervalArithmetic b) {
        return (
            this.leftLimit.compareTo(b.getLeftLimit()) >= 0
            && this.rightLimit.compareTo(b.getRightLimit()) <= 0
        );
    }

    @Override
    public String toString() {
        return "[" + this.leftLimit + ";" + this.rightLimit + "]";
    }

    /**
     * Calculates positive part of x
     * @param x BigDecimal
     * @return BigDecimal
     */
    public BigDecimal positivePart(BigDecimal x) {
        return x.max(BigDecimal.ZERO);
    }
    
    /**
     * Calculates negative part of x
     * @param x BigDecimal
     * @return BigDecimal
     */
    public BigDecimal negativePart(BigDecimal x) {        
        return x.negate().max(BigDecimal.ZERO);
    }
    
    /**
     * Calculates infimum by order 
     * @param a IntervalArithmetic
     * @return KaucherIntervalArithmetic
     */
    @Override    
    public KaucherIntervalArithmetic inf(IntervalArithmetic a) {
        return new KaucherIntervalArithmetic(
            this.getLeftLimit().max(a.getLeftLimit()),
            this.getRightLimit().min(a.getRightLimit()),
            this.intervalRoundingMode
        );
    }
    
    /**
     * Calculates supremum by order 
     * @param a IntervalArithmetic
     * @return KaucherIntervalArithmetic
     */
    @Override
    public KaucherIntervalArithmetic sup(IntervalArithmetic a) {
        return new KaucherIntervalArithmetic(
            this.getLeftLimit().min(a.getLeftLimit()),
            this.getRightLimit().max(a.getRightLimit()),
            this.intervalRoundingMode            
        );
    }

    private void roundInterval() {
        if (this.leftLimit.scale() > this.scale) {
            if (this.isProper()) {
                this.leftLimit = this.leftLimit.setScale(
                    this.scale, 
                    this.properLeftRoundingMode
                );    
            } else {
                this.leftLimit = this.leftLimit.setScale(
                    this.scale,
                    this.improperLeftRoundingMode
                );
            }
        }
        if (this.rightLimit.scale() > this.scale) {
            if (this.isProper()) {
                this.rightLimit = this.rightLimit.setScale(
                    this.scale,
                    this.properRightRoundingMode
                );    
            } else {
                this.rightLimit = this.rightLimit.setScale(
                    this.scale,
                    this.improperRightRoundingMode
                );
            }
        }     
    }

    private void roundingConfiguration(IntervalRoundingMode mode) throws IntervalException {
        switch (mode) {
            case OUTWARD:
                properLeftRoundingMode = RoundingMode.FLOOR;
                properRightRoundingMode = RoundingMode.CEILING;
                improperLeftRoundingMode = RoundingMode.FLOOR;
                improperRightRoundingMode = RoundingMode.CEILING;
            break;
            case INWARD:
                properLeftRoundingMode = RoundingMode.CEILING;
                properRightRoundingMode = RoundingMode.FLOOR;
                improperLeftRoundingMode = RoundingMode.CEILING;
                improperRightRoundingMode = RoundingMode.FLOOR;
            break;
            case MIXED:
                properLeftRoundingMode = RoundingMode.FLOOR;
                properRightRoundingMode = RoundingMode.CEILING;
                improperLeftRoundingMode = RoundingMode.CEILING;
                improperRightRoundingMode = RoundingMode.FLOOR;
            break;
            default:
                throw new IntervalException("Unknown rounding type");
        }
    }

    @Override
    public BigDecimal getLeftLimit() {
        return this.leftLimit;
    }

    @Override
    public BigDecimal getRightLimit() {
        return this.rightLimit;
    }  

    @Override
    public int getScale() {
        return this.scale;
    }

    @Override
    public void setLimits(BigDecimal left, BigDecimal right) {
        this.leftLimit = left;
        this.rightLimit = right;
        this.scale = Math.max(this.leftLimit.scale(), this.rightLimit.scale());
        this.roundInterval();
    }
    
    @Override
    public void setLimits(BigDecimal left, BigDecimal right, int scale) {
        this.leftLimit = left;
        this.rightLimit = right;
        this.scale = scale;
        this.roundInterval();
    }

    public IntervalRoundingMode getIntervalRoundingMode() {
        return this.intervalRoundingMode;
    }
    
    public RoundingMode getImproperLeftRoundingMode() {
        return improperLeftRoundingMode;
    }

    public RoundingMode getImproperRightRoundingMode() {
        return improperRightRoundingMode;
    }

    public RoundingMode getProperLeftRoundingMode() {
        return properLeftRoundingMode;
    }

    public RoundingMode getProperRightRoundingMode() {
        return properRightRoundingMode;
    }



}
